use serde::{Deserialize, Serialize};

use crate::Error;

#[derive(Debug, Clone, Deserialize)]
pub struct CreateServiceResponse {
    #[serde(rename = "serviceId")]
    pub service_id: String,
}

#[derive(Debug, Clone, Deserialize)]
pub struct CreateServiceInstanceResponse {
    #[serde(rename = "instanceId")]
    pub instance_id: String,
}

#[derive(Debug, Clone, Deserialize)]
pub struct FindServiceInstanceResponse {
    pub instances: Vec<ServiceInstanceResponse>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct ServiceInstanceResponse {
    #[serde(rename = "serviceId")]
    pub service_id: String,
    #[serde(rename = "instanceId")]
    pub instance_id: String,
    pub version: String,
    #[serde(rename = "hostName")]
    pub host_name: String,
    pub endpoints: Vec<String>,
    pub status: String,
    #[serde(rename = "healthCheck")]
    pub health_check: ServiceInstanceCheckHealthResponse,
    pub framework: Option<ServiceInstanceFrameworkResponse>,
    /// ts
    pub timestamp: String,
    /// ts
    #[serde(rename = "modTimestamp")]
    pub mod_timestamp: String,
}

impl ServiceInstanceResponse {
    /// return: http://ip:port
    pub fn get_best_addr(&self) -> Option<String> {
        if self.endpoints.is_empty() {
            return None;
        }
        let r = rand::random::<u8>();
        let index = r as usize % self.endpoints.len();
        Some(format!(
            "http://{}",
            get_addr_from_endpoint(self.endpoints[index].as_str())
        ))
    }
}

pub fn get_addr_from_endpoint(ep: &str) -> &str {
    if let Some(pos) = ep.find("://") {
        &ep[pos + 3..]
    } else {
        ep
    }
}

#[derive(Debug, Clone, Deserialize)]
pub struct ServiceInstanceCheckHealthResponse {
    pub mode: String,
    pub interval: i32,
    pub times: i32,
}

#[derive(Debug, Clone, Deserialize)]
pub struct ServiceInstanceFrameworkResponse {
    pub name: String,
    pub version: String,
}

#[derive(Debug, Clone, Serialize)]
pub struct ServiceSchemaInfo {
    #[serde(rename = "schemaId")]
    pub schema_id: String,
    pub schema: String,
    /// MUST 1-128 \W, not include white char
    pub summary: String,
}

impl ServiceSchemaInfo {
    pub fn new(id: &str, schema: String) -> Self {
        Self {
            schema_id: id.to_owned(),
            schema,
            summary: id.to_owned(),
        }
    }
}

#[derive(Debug, Clone, Deserialize)]
pub struct CommonResponse {
    #[serde(rename = "errorCode")]
    pub error_code: String,
    #[serde(rename = "errorMessage")]
    pub error_message: String,
    pub detail: String,
}

impl CommonResponse {
    pub fn is_ok(&self) -> bool {
        self.code() == 0
    }

    pub fn code(&self) -> i32 {
        self.error_code.parse::<i32>().unwrap_or(-1)
    }
}

impl From<CommonResponse> for Error {
    fn from(value: CommonResponse) -> Self {
        Error::Api(
            value.code(),
            format!("{}:{}", value.error_message, value.detail),
        )
    }
}

#[repr(u32)]
#[derive(Clone, Debug)]
pub enum InstanceStatus {
    Down = 0,
    Starting,
    Up,
    OutOfService,
    Testing,
}

impl ToString for InstanceStatus {
    fn to_string(&self) -> String {
        match self {
            Self::Down => "DOWN",
            Self::Starting => "STARTING",
            Self::Up => "UP",
            Self::OutOfService => "OUTOFSERVICE",
            Self::Testing => "TESTING",
        }
        .to_owned()
    }
}

impl From<&str> for InstanceStatus {
    fn from(value: &str) -> Self {
        match value.to_ascii_uppercase().as_str() {
            "STARTING" => Self::Starting,
            "UP" => Self::Up,
            "OUTOFSERVICE" => Self::OutOfService,
            "TESTING" => Self::Testing,
            _ => Self::Down,
        }
    }
}

#[derive(Debug, Clone)]
pub struct AddServiceInfo {
    pub service_name: String,
    pub service_version: String,
    pub addr: String,
    pub schemes: Vec<ServiceSchemaInfo>,
}
